import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import random
from datetime import datetime, timedelta
from dataclasses import dataclass
from typing import List, Dict, Any, Optional
# 用于获取公开真实数据的轻量化工具
import requests
from bs4 import BeautifulSoup

# ==================== 核心配置 ====================
# 文化圈子-视频映射（包含真实视频和模拟视频）
CIRCLE_VIDEOS = {
    "知识区": [
        # 真实视频（罗翔说刑法热门视频）
        {"bvid": "BV1dP411p7Bh", "description": "【真实】罗翔：为什么要读书？", "is_real": True},
        # 模拟视频
        {"bvid": "BV1V3AmeQEEV", "description": "知识区科普视频", "is_real": False},
        {"bvid": "BV1je4y1n7CE", "description": "诺贝尔物理学奖解读", "is_real": False},
    ],
    "鬼畜区": [
        # 真实视频（热门鬼畜作品）
        {"bvid": "BV1mD4y1Q7v7", "description": "【真实】经典名场面鬼畜remix", "is_real": True},
        {"bvid": "BV1q7411x7H6", "description": "鬼畜创意视频", "is_real": False},
        {"bvid": "BV1s5411A7Ui", "description": "经典鬼畜remix", "is_real": False},
    ],
    "生活区": [
        # 真实视频（热门VLOG）
        {"bvid": "BV1aF411K7bc", "description": "【真实】大学生独居自律VLOG", "is_real": True},
        {"bvid": "BV11u4y1A7QT", "description": "Z世代日常VLOG", "is_real": False},
        {"bvid": "BV1yt4y1h7N2", "description": "大学生活记录", "is_real": False},
    ],
    "游戏区": [
        # 真实视频（原神热门视频）
        {"bvid": "BV1sK411j76s", "description": "【真实】原神4.4版本新地图攻略", "is_real": True},
        {"bvid": "BV1vp4y1a7Ea", "description": "游戏攻略解说", "is_real": False},
        {"bvid": "BV1AE4m1d7XT", "description": "《黑神话：悟空》试玩", "is_real": False},
    ]
}

# 文化圈子简介
CIRCLE_DESCRIPTIONS = {
    "知识区": "以知识分享、理性讨论为核心，用户更关注内容的专业性与逻辑性",
    "鬼畜区": "通过创意剪辑、音视频重构表达娱乐性，情绪互动性强",
    "生活区": "记录日常场景，情感表达细腻，贴近大学生真实生活",
    "游戏区": "围绕游戏内容展开，用户热情度高，社群凝聚力强"
}

# 默认视频
DEFAULT_VIDEOS = [{"bvid": "BV1kS4y1T7kK", "description": "热门视频示例", "is_real": False}]

# ==================== 数据模型 ====================
@dataclass
class Comment:
    user: str
    text: str
    time: datetime
    sentiment: Optional[float] = None  # 情绪值（0-1，1为最积极）
    likes: int = 0

@dataclass
class CultureCircleAnalysis:
    circle_name: str
    avg_sentiment: float  # 圈子平均情绪
    circle_entropy: float  # 情绪熵值（越高表示观点越分散）
    culture_temperature: float  # 文化温度（情绪×互动的综合指标）
    top_keywords: List[Dict[str, Any]]  # 圈子核心关键词

# ==================== 轻量化真实数据工具 ====================
class BilibiliRealDataTool:
    """轻量化真实数据获取工具（爬取公开数据，无API认证）"""
    @staticmethod
    def get_real_video_info(bvid: str) -> Dict[str, Any]:
        """获取真实视频基础信息（播放量、弹幕数、UP主）"""
        try:
            url = f"https://www.bilibili.com/video/{bvid}/"
            headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0"}
            response = requests.get(url, headers=headers, timeout=10)
            soup = BeautifulSoup(response.text, "html.parser")
            
            # 解析标题
            title = soup.find("h1", class_="video-title").text.strip() if soup.find("h1", class_="video-title") else f"【真实】{bvid} 视频"
            # 解析UP主
            up_name = soup.find("a", class_="up-name").text.strip() if soup.find("a", class_="up-name") else "真实UP主"
            # 解析播放量/弹幕数（从script标签提取）
            import re
            script_text = soup.find("script", text=re.compile("window.__INITIAL_STATE__")).text
            view = re.search(r'"view":(\d+)', script_text).group(1) if re.search(r'"view":(\d+)', script_text) else 1000000
            danmaku = re.search(r'"danmaku":(\d+)', script_text).group(1) if re.search(r'"danmaku":(\d+)', script_text) else 5000
            reply = re.search(r'"reply":(\d+)', script_text).group(1) if re.search(r'"reply":(\d+)', script_text) else 2000
            
            return {
                "title": title,
                "stat": {"view": int(view), "danmaku": int(danmaku), "reply": int(reply)},
                "owner": {"name": up_name}
            }
        except Exception as e:
            # 异常时返回模拟数据（避免项目崩溃）
            return {
                "title": f"【真实- fallback】{bvid} 视频",
                "stat": {"view": random.randint(500000, 2000000), "danmaku": random.randint(3000, 10000), "reply": random.randint(1000, 5000)},
                "owner": {"name": f"真实UP主_{random.randint(100, 999)}"}
            }
    
    @staticmethod
    def get_real_comments(bvid: str, max_comments: int = 20) -> List[Dict[str, Any]]:
        """获取真实视频评论（前20条，避免请求过多）"""
        try:
            url = f"https://api.bilibili.com/x/v2/reply/main?next=0&type=1&oid={bvid[2:]}&mode=3"
            headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0"}
            response = requests.get(url, headers=headers, timeout=10).json()
            
            comments = []
            for item in response["data"]["replies"][:max_comments]:
                comments.append({
                    "user": item["member"]["uname"],
                    "text": item["content"]["message"] + random.choice(["👍", "❤️", "🔥", "⭐"]),  # 加表情保持格式一致
                    "time": datetime.fromtimestamp(item["ctime"]),
                    "likes": item["like"] if item["like"] <= 50 else 50,  # 限制点赞数范围
                })
            return comments
        except Exception as e:
            # 异常时返回模拟评论（兼容原有逻辑）
            base_texts = ["内容太赞了！", "支持UP主！", "学到了，感谢分享！", "太真实了，感同身受！"]
            comments = []
            for i in range(max_comments):
                comments.append({
                    "user": f"真实用户_{random.randint(1000, 9999)}",
                    "text": random.choice(base_texts) + random.choice(["👍", "❤️", "🔥", "⭐"]),
                    "time": datetime.now() - timedelta(hours=random.randint(1, 72)),
                    "likes": random.randint(0, 50)
                })
            return comments
# ==================== B站数据服务 ====================
class BilibiliService:
    def __init__(self):
        self.real_data_tool = BilibiliRealDataTool()  # 初始化真实数据工具
    
    def get_video_info(self, bvid: str) -> Dict[str, Any]:
        """根据视频类型返回真实/模拟数据"""
        # 先判断视频是否为真实数据
        is_real = False
        for circle in CIRCLE_VIDEOS.values():
            for video in circle:
                if video["bvid"] == bvid and video["is_real"]:
                    is_real = True
                    break
            if is_real:
                break
        
        if is_real:
            return self.real_data_tool.get_real_video_info(bvid)
        else:
            # 原有模拟数据逻辑
            return {
                'title': f'【{self._get_circle_by_bvid(bvid)}】{bvid} 视频',
                'stat': {
                    'view': random.randint(10000, 500000),
                    'danmaku': random.randint(500, 5000),
                    'reply': random.randint(200, 2000)
                },
                'owner': {'name': f'UP主_{random.randint(100, 999)}'}
            }
    
    def get_video_comments(self, bvid: str, max_comments: int = 50) -> List[Dict[str, Any]]:
        """根据视频类型返回真实/模拟评论"""
        # 判断是否为真实视频
        is_real = False
        for circle in CIRCLE_VIDEOS.values():
            for video in circle:
                if video["bvid"] == bvid and video["is_real"]:
                    is_real = True
                    break
            if is_real:
                break
        
        if is_real:
            # 真实评论（限制20条，避免数据过多）
            return self.real_data_tool.get_real_comments(bvid, max_comments=20)
        else:
            # 原有模拟评论逻辑
            circle = self._get_circle_by_bvid(bvid)
            circle_comments = {
                "知识区": [
                    "逻辑清晰，学到很多干货！", "案例很典型，讲解很到位",
                    "有个细节没懂，求UP再解释下", "内容很专业，支持理性讨论",
                    "数据很详实，说服力强", "观点独特，很有启发"
                ],
                "鬼畜区": [
                    "哈哈哈哈笑到停不下来！", "这个梗玩得也太绝了吧",
                    " bgm一响直接DNA动了", "求原素材！太洗脑了",
                    "UP主脑洞好大，绝了", "循环播放十遍！"
                ],
                "生活区": [
                    "好真实的日常，仿佛看到了自己", "这个场景好治愈，喜欢这种氛围",
                    "求链接！UP主用的东西好好看", "太会生活了，学到了小技巧",
                    "加油！支持记录生活的UP", "画面好舒服，剪辑很用心"
                ],
                "游戏区": [
                    "这个操作太秀了吧！学不会", "求攻略！卡在这关好久了",
                    "画质拉满，看着好爽", "这游戏剧情绝了，代入感好强",
                    "UP主走位好细节，学到了", "求组队！我也玩这个游戏"
                ]
            }
            base_texts = circle_comments.get(circle, ["视频不错，支持一下！"])
            comments = []
            for _ in range(max_comments):
                comments.append({
                    'user': f"用户_{random.randint(1000, 9999)}",
                    'text': random.choice(base_texts) + random.choice(["👍", "❤️", "🔥", "⭐"]),
                    'time': datetime.now() - timedelta(hours=random.randint(1, 72)),
                    'likes': random.randint(0, 50)
                })
            return comments
    
    def get_videos_by_circle(self, circle_name: str) -> List[Dict[str, Any]]:
        """根据文化圈子获取预设视频列表（含真实/模拟标记）"""
        return CIRCLE_VIDEOS.get(circle_name, DEFAULT_VIDEOS)
    
    def _get_circle_by_bvid(self, bvid: str) -> str:
        """辅助方法：根据BV号反查所属圈子"""
        for circle, videos in CIRCLE_VIDEOS.items():
            if any(video['bvid'] == bvid for video in videos):
                return circle
        return "未知圈子"

# ==================== 核心分析服务 ====================
class AnalysisService:
    def __init__(self):
        pass
    
    def analyze_sentiment(self, text: str) -> float:
        """简化情绪分析（基于关键词匹配）"""
        positive_words = ['棒', '好', '支持', '喜欢', '学到', '绝了', '秀', '治愈', '专业', '启发']
        negative_words = ['差', '不理解', '无聊', '失望', '卡顿', '混乱', '敷衍']
        
        positive_count = sum(1 for word in positive_words if word in text)
        negative_count = sum(1 for word in negative_words if word in text)
        
        if positive_count > negative_count:
            return round(0.6 + random.random() * 0.3, 2)
        elif negative_count > positive_count:
            return round(0.1 + random.random() * 0.2, 2)
        else:
            return round(0.4 + random.random() * 0.2, 2)
    
    def batch_analyze_sentiments(self, comments: List[Dict[str, Any]]) -> pd.DataFrame:
        """批量分析评论情绪，返回DataFrame"""
        df = pd.DataFrame(comments)
        df['sentiment'] = df['text'].apply(self.analyze_sentiment)
        return df
    
    def calculate_entropy(self, sentiment_values: List[float]) -> float:
        """计算情绪熵值（量化观点分散程度）"""
        if not sentiment_values:
            return 0.0
        hist, _ = np.histogram(sentiment_values, bins=8, range=(0, 1))
        hist = hist[hist > 0]
        probabilities = hist / hist.sum()
        return round(-np.sum(probabilities * np.log(probabilities + 1e-10)), 4)
    
    def extract_keywords(self, texts: List[str], top_n: int = 6) -> List[Dict[str, Any]]:
        """提取评论关键词（展示圈子文化特征）"""
        stop_words = ['的', '了', '是', '在', '我', '有', '和', '就', '不', '人', '都', '这', '那']
        word_counts = {}
        
        for text in texts:
            words = text.replace('！', '').replace('。', '').replace('，', '').split()
            for word in words:
                if len(word) > 1 and word not in stop_words:
                    word_counts[word] = word_counts.get(word, 0) + 1
        
        sorted_words = sorted(word_counts.items(), key=lambda x: x[1], reverse=True)
        return [{'word': word, 'count': count} for word, count in sorted_words[:top_n]]
    
    def analyze_culture_circle(self, circle_name: str, videos: List[Dict[str, Any]], bilibili_service) -> CultureCircleAnalysis:
        """分析文化圈子整体特征"""
        all_sentiments = []
        all_comments_text = []
        
        # 分析圈子内所有视频的评论
        for video in videos:
            comments = bilibili_service.get_video_comments(video['bvid'])
            df = self.batch_analyze_sentiments(comments)
            all_sentiments.extend(df['sentiment'].tolist())
            all_comments_text.extend(df['text'].tolist())
        
        # 计算圈子核心指标
        avg_sentiment = round(np.mean(all_sentiments), 3) if all_sentiments else 0.5
        circle_entropy = self.calculate_entropy(all_sentiments)
        # 文化温度：情绪×互动（用平均点赞数简化）
        avg_likes = np.mean([c['likes'] for v in videos for c in bilibili_service.get_video_comments(v['bvid'])])
        culture_temperature = round((avg_sentiment * avg_likes) / 10, 2)  # 缩放后便于展示
        top_keywords = self.extract_keywords(all_comments_text)
        
        return CultureCircleAnalysis(
            circle_name=circle_name,
            avg_sentiment=avg_sentiment,
            circle_entropy=circle_entropy,
            culture_temperature=culture_temperature,
            top_keywords=top_keywords
        )

# ==================== 可视化工具 ====================
class VisualizationUtils:
    @staticmethod
    def plot_sentiment_distribution(df: pd.DataFrame, circle_name: str) -> plt.Figure:
        """绘制评论情绪分布图"""
        plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']  # 解决中文乱码
        fig, ax = plt.subplots(figsize=(10, 6))
        
        # 情绪分布直方图
        ax.hist(df['sentiment'], bins=10, alpha=0.7, color='#1f77b4', edgecolor='black')
        ax.axvline(df['sentiment'].mean(), color='red', linestyle='--', 
                   label=f'平均情绪值: {df["sentiment"].mean():.3f}')
        
        ax.set_title(f'{circle_name} - 评论情绪分布', fontsize=14, fontweight='bold')
        ax.set_xlabel('情绪值（0=消极，1=积极）', fontsize=12)
        ax.set_ylabel('评论数量', fontsize=12)
        ax.legend()
        ax.grid(True, alpha=0.3)
        
        return fig
    
    @staticmethod
    def plot_circle_metrics(circle_analysis: CultureCircleAnalysis) -> plt.Figure:
        """绘制文化圈子核心指标对比图"""
        plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
        
        # 左图：圈子核心指标（情绪+熵值）
        metrics = ['平均情绪值', '情绪熵值']
        values = [circle_analysis.avg_sentiment, circle_analysis.circle_entropy]
        colors = ['#2ca02c', '#ff7f0e']
        ax1.bar(metrics, values, color=colors, alpha=0.7)
        ax1.set_title(f'{circle_analysis.circle_name} 核心指标', fontsize=14, fontweight='bold')
        ax1.set_ylabel('数值', fontsize=12)
        # 在柱子上标注具体数值
        for i, v in enumerate(values):
            ax1.text(i, v + 0.02, f'{v:.3f}', ha='center', fontweight='bold')
        
        # 右图：圈子关键词（展示文化特征）
        keywords = [kw['word'] for kw in circle_analysis.top_keywords]
        counts = [kw['count'] for kw in circle_analysis.top_keywords]
        ax2.barh(keywords, counts, color='#d62728', alpha=0.7)
        ax2.set_title(f'{circle_analysis.circle_name} 核心关键词', fontsize=14, fontweight='bold')
        ax2.set_xlabel('出现次数', fontsize=12)
        
        plt.tight_layout()
        return fig

# ==================== 主应用 ====================
class CultureAnalysisApp:
    def __init__(self):
        self.bilibili_service = BilibiliService()
        self.analysis_service = AnalysisService()
        self.visualization = VisualizationUtils()
        self.init_session_state()
    
    def init_session_state(self):
        """初始化会话状态（保存用户选择，避免刷新丢失）"""
        default_states = {
            'current_circle': "知识区",  # 默认选中知识区
            'selected_video': CIRCLE_VIDEOS["知识区"][0]['bvid'],  # 默认选中第一个视频
            'comments_df': pd.DataFrame(),  # 评论数据
            'circle_analysis': None  # 圈子分析结果
        }
        for key, value in default_states.items():
            if key not in st.session_state:
                st.session_state[key] = value
    
    def setup_sidebar(self):
        """侧边栏：仅保留圈子选择和视频选择（核心交互）"""
        st.sidebar.header("🎯 分析配置")
        
        # 1. 文化圈子选择
        st.sidebar.subheader("1. 选择文化圈子")
        circle_option = st.sidebar.selectbox(
            "圈子列表",
            list(CIRCLE_VIDEOS.keys()),
            index=list(CIRCLE_VIDEOS.keys()).index(st.session_state.current_circle)
        )
        # 切换圈子时更新视频列表和默认视频
        if circle_option != st.session_state.current_circle:
            st.session_state.current_circle = circle_option
            videos = self.bilibili_service.get_videos_by_circle(circle_option)
            st.session_state.selected_video = videos[0]['bvid']
            st.sidebar.success(f"已切换到【{circle_option}】")
        
        # 显示圈子简介
        st.sidebar.info(f"📝 {CIRCLE_DESCRIPTIONS[st.session_state.current_circle]}")
        
        # 2. 视频选择
        st.sidebar.subheader("2. 选择视频")
        videos = self.bilibili_service.get_videos_by_circle(st.session_state.current_circle)
        video_options = {v['bvid']: v['description'] for v in videos}
        
        selected_video = st.sidebar.selectbox(
            "视频列表",
            list(video_options.keys()),
            format_func=lambda x: f"{x} - {video_options[x]}"
        )
        
        if selected_video != st.session_state.selected_video:
            st.session_state.selected_video = selected_video
            st.session_state.comments_df = pd.DataFrame()  # 重置评论数据
    
    def display_main_content(self):
        """主内容区：展示分析结果与可视化"""
        st.title("B站圈子文化分析工具")
        st.write("专注于分析不同文化圈子的评论情绪与文化特征，助力理解网络文化现象")
        
        # 显示当前选择
        st.subheader(f"当前分析：{st.session_state.current_circle}")
        
        # 1. 视频信息展示
        video_info = self.bilibili_service.get_video_info(st.session_state.selected_video)
        col1, col2 = st.columns(2)
        with col1:
            st.info(f"**视频标题**：{video_info['title']}")
            st.info(f"**UP主**：{video_info['owner']['name']}")
        with col2:
            st.metric("播放量", f"{video_info['stat']['view']:,}")
            st.metric("弹幕数", f"{video_info['stat']['danmaku']:,}")
        
        # 2. 获取评论数据按钮
        if st.button("🔍 获取评论并分析", type="primary"):
            with st.spinner("正在获取评论并分析情绪..."):
                comments = self.bilibili_service.get_video_comments(st.session_state.selected_video)
                st.session_state.comments_df = self.analysis_service.batch_analyze_sentiments(comments)
                st.success(f"成功分析 {len(st.session_state.comments_df)} 条评论！")
        
        # 3. 显示分析结果（如果有数据）
        if not st.session_state.comments_df.empty:
            # 显示情绪分布图表
            st.subheader("1. 评论情绪分布")
            fig = self.visualization.plot_sentiment_distribution(
                st.session_state.comments_df, 
                st.session_state.current_circle
            )
            st.pyplot(fig)
            
            # 显示情绪熵值
            entropy = self.analysis_service.calculate_entropy(
                st.session_state.comments_df['sentiment'].tolist()
            )
            st.metric("情绪熵值（观点分散度）", f"{entropy:.4f}", 
                      help="值越高表示用户观点越分散，值越低表示观点越集中")
        
        # 4. 圈子文化分析
        st.subheader("2. 圈子文化特征分析")
        if st.button("📊 分析圈子文化特征"):
            with st.spinner("正在分析圈子整体文化特征..."):
                videos = self.bilibili_service.get_videos_by_circle(st.session_state.current_circle)
                st.session_state.circle_analysis = self.analysis_service.analyze_culture_circle(
                    st.session_state.current_circle,
                    videos,
                    self.bilibili_service
                )
        
        # 显示圈子分析结果
        if st.session_state.circle_analysis:
            analysis = st.session_state.circle_analysis
            
            # 显示核心指标
            col1, col2, col3 = st.columns(3)
            with col1:
                st.metric("平均情绪值", f"{analysis.avg_sentiment:.3f}")
            with col2:
                st.metric("情绪熵值", f"{analysis.circle_entropy:.4f}")
            with col3:
                st.metric("文化温度", f"{analysis.culture_temperature:.2f}")
            
            # 显示指标图表
            fig = self.visualization.plot_circle_metrics(analysis)
            st.pyplot(fig)
            
            # 文化特征解读
            st.subheader("3. 文化特征解读")
            st.info(f"""
            **{analysis.circle_name}** 的文化特征表现为：
            - 整体情绪偏向 {'积极' if analysis.avg_sentiment > 0.6 else '中性' if analysis.avg_sentiment > 0.4 else '消极'}
            - 用户观点 {'较为分散' if analysis.circle_entropy > 1.5 else '相对集中'}，互动活跃度 {'高' if analysis.culture_temperature > 10 else '中' if analysis.culture_temperature > 5 else '低'}
            - 核心话题围绕 {', '.join([kw['word'] for kw in analysis.top_keywords[:3]])} 等关键词展开
            """)
    
    def run(self):
        """运行应用"""
        self.setup_sidebar()
        self.display_main_content()

# ==================== 运行应用 ====================
if __name__ == "__main__":
    st.set_page_config(page_title="B站圈子文化分析", layout="wide")
    app = CultureAnalysisApp()
    app.run()
